package org.zjvis.datascience.service.graph;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.tinkerpop.gremlin.process.traversal.*;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.structure.*;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.util.GraphFactory;
import org.janusgraph.core.*;
import org.janusgraph.core.schema.JanusGraphIndex;
import org.janusgraph.core.schema.JanusGraphManagement;
import org.janusgraph.core.schema.RelationTypeIndex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import org.springframework.beans.factory.annotation.Value;
import org.zjvis.datascience.common.dto.graph.GraphFilterDTO;
import org.zjvis.datascience.common.graph.enums.GraphFilterSubTypeEnum;
import org.zjvis.datascience.common.graph.enums.GraphFilterTypeEnum;
import org.zjvis.datascience.common.graph.util.GraphUtil;
import org.zjvis.datascience.common.graph.algo.*;
import org.zjvis.datascience.common.graph.filter.FilterResult;
import org.zjvis.datascience.common.graph.model.*;
import org.zjvis.datascience.common.vo.graph.*;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description JanusGraph的嵌入单例模式
 * @date 2021-12-29
 */
@Service
public class JanusGraphEmbedService {
    private static final Logger logger = LoggerFactory.getLogger(JanusGraphEmbedService.class);

    @Value("${janusgraph.conf}")
    protected String propFileName;

    private Configuration conf;

    protected JanusGraph graph;

    protected GraphTraversalSource g;

    private String mixedIndexConfigName;
    public static final String domainPropKeyName = "_graphId_";
    public static final String refIdPropKeyName = "_refId_";
    public static final String orderPropKeyName = "_orderId_";
    public static final String edgeWeightPropKeyName = "_weight_";
    public static final String edgeDirectedPropKeyName = "_directed_";
    public static final String edgeVisiblePropKeyName = "_visible_";
    public static final String edgeLoopPropKeyName = "_loop_";

    public static final String categoryIdPropKeyName = "_categoryId_";
    public static final String edgeIdPropKeyName = "_edgeId_";


    @PostConstruct
    public void init() {
        logger.info("init JanusGraphEmbedService...");
        try {
            conf = new PropertiesConfiguration(propFileName);
//            mixedIndexConfigName = conf.getString("index.search.index-name");
            openGraph();
            initSchema();
        } catch (Exception e) {
            logger.error("JanusGraphEmbedService error: {}", e.getMessage());
        }


    }

    private void openGraph() {
        long t1 = System.currentTimeMillis();
        logger.info("opening graph...");
        graph = (JanusGraph) GraphFactory.open(conf);
        g = graph.traversal();
        long t2 = System.currentTimeMillis();
        logger.info("JanusGraphEmbedService openGraph in {} ms.", (t2 - t1));
    }

    @PreDestroy
    private void closeGraph() throws Exception {
        logger.info("closing graph...");
        try {
            if (g != null) {
                g.close();
            }
            if (graph != null) {
                graph.close();
            }
        } finally {
            g = null;
            graph = null;
        }
    }

    private void dropGraph() throws Exception {
        logger.info("dropping graph...");
        try {
            if (g != null) {
                g.close();
            }
            if (graph != null) {
                JanusGraphFactory.drop(graph);
            }
        } finally {
            graph = null;
            g = null;
        }
    }

    private void initSchema() {
        JanusGraphManagement mgnt = graph.openManagement();
        createProperty(mgnt, domainPropKeyName, Long.class);
        createProperty(mgnt, refIdPropKeyName, String.class);
        createProperty(mgnt, orderPropKeyName, Integer.class);
        createProperty(mgnt, edgeWeightPropKeyName, Double.class);
        createProperty(mgnt, edgeDirectedPropKeyName, Boolean.class);
        createProperty(mgnt, edgeVisiblePropKeyName, Boolean.class);
        createProperty(mgnt, categoryIdPropKeyName, String.class);
        createProperty(mgnt, edgeIdPropKeyName, String.class);
        createProperty(mgnt, edgeLoopPropKeyName, Boolean.class);

        //vertex index
        createCompositeIndex(mgnt, domainPropKeyName, true);
        createCompositeIndex(mgnt, refIdPropKeyName, true);
        createCompositeIndex(mgnt, categoryIdPropKeyName, true);

        //edge index
        createCompositeIndex(mgnt, domainPropKeyName, false);
        createCompositeIndex(mgnt, refIdPropKeyName, false);
        createCompositeIndex(mgnt, edgeIdPropKeyName, false);
        createCompositeIndex(mgnt, edgeVisiblePropKeyName, false);
        createCompositeIndex(mgnt, edgeLoopPropKeyName, false);

        //metrics
        createProperty(mgnt, MetricResultManager.DegreeTag, Double.class);
        createProperty(mgnt, MetricResultManager.WeightedDegreeTag, Double.class);
        createProperty(mgnt, MetricResultManager.EigenvectorCentralityTag, Double.class);
        createProperty(mgnt, MetricResultManager.BetweennessCentralityTag, Double.class);
        createProperty(mgnt, MetricResultManager.ClosenessCentralityTag, Double.class);
        createProperty(mgnt, MetricResultManager.EccentricityTag, Double.class);
        createProperty(mgnt, MetricResultManager.PageRankTag, Double.class);
        createProperty(mgnt, MetricResultManager.ClusterTag, String.class);
        createProperty(mgnt, MetricResultManager.StronglyConnectedComponentsTag, Integer.class);
        createProperty(mgnt, MetricResultManager.ConnectedComponentsTag, Integer.class);
        createProperty(mgnt, MetricResultManager.WeaklyConnectedComponentsTag, Integer.class);
        createProperty(mgnt, MetricResultManager.ClusteringCoefficientTag, Double.class);
        createProperty(mgnt, MetricResultManager.TrianglesTag, Integer.class);
        mgnt.commit();
    }

    private VertexLabel createVertexLabel(JanusGraphManagement management, String labelName) {
        if (!management.containsVertexLabel(labelName)) {
            return management.makeVertexLabel(labelName).make();
        }
        return null;
    }

    private EdgeLabel createEdgeLabel(JanusGraphManagement management, String labelName) {
        if (!management.containsEdgeLabel(labelName)) {
            return management.makeEdgeLabel(labelName).make();
        }
        return null;
    }

    private PropertyKey createProperty(JanusGraphManagement management, String propertyKeyName, Class<?> dataType) {
        if (!management.containsPropertyKey(propertyKeyName)) {
            return management.makePropertyKey(propertyKeyName).dataType(dataType).make();
        }
        return null;
    }

    private PropertyKey createProperty(JanusGraphManagement management, String propertyKeyName, Class<?> dataType, Cardinality cardinality) {
        if (!management.containsPropertyKey(propertyKeyName)) {
            if (cardinality == null) {
                return createProperty(management, propertyKeyName, dataType);
            }
            return management.makePropertyKey(propertyKeyName).dataType(dataType).cardinality(cardinality).make();
        }
        return null;
    }

    private JanusGraphIndex createCompositeIndex(JanusGraphManagement management, String propertyKeyName, boolean isVertex) {
        String indexName = propertyKeyName + "index_";
        if (!isVertex) {
            indexName = propertyKeyName + "edge_index_";
        }
        if (!management.containsGraphIndex(indexName)) {
            Class<? extends Element> elementType = isVertex ? Vertex.class: Edge.class;
            return
                    management.buildIndex(indexName, elementType)
                            .addKey(management.getPropertyKey(propertyKeyName))
                            .buildCompositeIndex();
        }
        return null;
    }

    private JanusGraphIndex createMixedIndex(JanusGraphManagement management, String propertyKeyName, boolean isVertex) {
        String indexName = propertyKeyName + "mixedIndex_";
        if (!isVertex) {
            indexName = propertyKeyName + "edge_mixedIndex_";
        }
        if (!management.containsGraphIndex(indexName)) {
            Class<? extends Element> elementType = isVertex ? Vertex.class: Edge.class;
            return
                    management.buildIndex(indexName, elementType)
                            .addKey(management.getPropertyKey(propertyKeyName))
                            .buildMixedIndex(mixedIndexConfigName);
        }
        return null;
    }

    private JanusGraphIndex createMixedIndex(JanusGraphManagement management, String indexName, boolean isVertex, String... propertyKeyNames) {
        if (!management.containsGraphIndex(indexName)) {
            Class<? extends Element> elementType = isVertex ? Vertex.class: Edge.class;
            JanusGraphManagement.IndexBuilder builder = management.buildIndex(indexName, elementType);
            for (String propertyKeyName: propertyKeyNames) {
                builder = builder.addKey(management.getPropertyKey(propertyKeyName));
            }
            return builder.buildMixedIndex(mixedIndexConfigName);
        }
        return null;
    }

    private RelationTypeIndex createRelationTypeIndex(JanusGraphManagement management, String indexName, String edgeLabel, String propertyKeyNames) {
        if (!management.containsRelationIndex(management.getEdgeLabel(edgeLabel), indexName)) {
            PropertyKey key = management.getPropertyKey(propertyKeyNames);
            EdgeLabel label = management.getEdgeLabel(edgeLabel);
            return management.buildEdgeIndex(label, indexName, Direction.BOTH, key);
        }
        return null;
    }

    private void createSchema(List<CategoryVO> categoryVOList, List<EdgeVO> edgeVOList, Long graphId, boolean isBuild) {
        JanusGraphManagement mgnt = graph.openManagement();
        try {
            // naive check if the schema was previously created
            for (CategoryVO category: categoryVOList) {
                if (category.getOriginLabel() != null) {
                    createVertexLabel(mgnt, GraphUtil.aliasPropertyKey(category.getOriginLabel().getName(), graphId));
                }
                if (category.getKeyAttr() != null) {
                    Class<?> dataType = GraphUtil.sqlType2TypeClass(category.getKeyAttr().getType());
                    Cardinality cardinality = isBuild ? Cardinality.SET : Cardinality.SINGLE;
                    createProperty(mgnt, GraphUtil.aliasPropertyKey(category.getKeyAttr().getName(), graphId), dataType, cardinality);
                }
                for (CategoryAttrVO attr: category.getAttrs()) {
                    Class<?> dataType = GraphUtil.sqlType2TypeClass(attr.getType());
                    Cardinality cardinality = isBuild ? Cardinality.SET : Cardinality.SINGLE;
                    createProperty(mgnt, GraphUtil.aliasPropertyKey(attr.getName(),graphId) , dataType, cardinality);
                }
            }
            for (EdgeVO edge: edgeVOList) {
                createEdgeLabel(mgnt, GraphUtil.aliasPropertyKey(edge.getLabel(), graphId));
                for (CategoryAttrVO attr: edge.getAttrs()) {
                    Class<?> dataType = GraphUtil.sqlType2TypeClass(attr.getType());
                    Cardinality cardinality = isBuild ? Cardinality.SET : Cardinality.SINGLE;
                    createProperty(mgnt, GraphUtil.aliasPropertyKey(attr.getName(), graphId), dataType, cardinality);
                }
            }
            mgnt.commit();
        } catch (Exception e) {
            mgnt.rollback();
        }
    }

    public Vertex createVertex(NodeVO node, String labelName, Long graphId) {
        List<AttrVO> attrs = node.getAttrs();
        GraphTraversal<Vertex, Vertex> gt = g.addV(GraphUtil.aliasPropertyKey(labelName, graphId))
                .property(refIdPropKeyName, node.getId())
                .property(domainPropKeyName, graphId)
                .property(orderPropKeyName, node.getOrderId())
                .property(categoryIdPropKeyName, labelName);
        for (AttrVO attr: attrs) {
            Object val = attr.getValue();
            if (val instanceof Set) {
                ((Set<?>) val).forEach(v->
                        gt.property(GraphUtil.aliasPropertyKey(attr.getKey(), graphId), v)
                );
            } else {
                gt.property(GraphUtil.aliasPropertyKey(attr.getKey(), graphId), attr.getValue());
            }
        }
        Vertex v = gt.next();
        return v;
    }

    public Edge createEdge(Vertex src, Vertex tar, LinkVO link, String labelName, Long graphId) {
        if (src == null) {
            src = g.V().has(refIdPropKeyName, link.getSource()).next();
        }
        if (tar == null) {
            tar = g.V().has(refIdPropKeyName, link.getTarget()).next();
        }
        List<AttrVO> attrs = link.getAttrs();
        GraphTraversal<Edge, Edge> gt = g.addE(GraphUtil.aliasPropertyKey(labelName, graphId)).from(src).to(tar)
                .property(domainPropKeyName, graphId)
                .property(refIdPropKeyName, link.getId())
                .property(edgeWeightPropKeyName, link.getWeight())
                .property(edgeDirectedPropKeyName, link.getDirected())
                .property(edgeVisiblePropKeyName, true)
                .property(orderPropKeyName, link.getOrderId())
                .property(edgeIdPropKeyName, labelName)
                .property(edgeLoopPropKeyName, link.getSource().equals(link.getTarget()));
        for (AttrVO attr: attrs) {
            Object val = attr.getValue();
            if (val instanceof Set) {
                ((Set<?>) val).forEach(v->
                        gt.property(GraphUtil.aliasPropertyKey(attr.getKey(), graphId), v)
                );
            } else {
                gt.property(GraphUtil.aliasPropertyKey(attr.getKey(), graphId), attr.getValue());
            }
        }
        if (link.getDirected()) {
            return gt.next();
        } else {
            Edge outE = gt.next();
            Edge inE = g.addE(GraphUtil.aliasPropertyKey(labelName, graphId)).from(tar).to(src)
                    .property(domainPropKeyName, graphId)
                    .property(refIdPropKeyName, link.getId())
                    .property(edgeWeightPropKeyName, link.getWeight())
                    .property(edgeDirectedPropKeyName, link.getDirected())
                    .property(edgeVisiblePropKeyName, false)
                    .property(orderPropKeyName, link.getOrderId())
                    .property(edgeIdPropKeyName, labelName)
                    .property(edgeLoopPropKeyName, link.getSource().equals(link.getTarget()))
                    .next();
            return outE;
        }

    }

    public void dropAllGraphNodes(Long graphId) {
        g.V().has(domainPropKeyName, graphId).drop().iterate();
        g.tx().commit();
        logger.info("drop success.   graphId = " + graphId);
    }

    public void dropAllGraphNodes(List<Long> graphIdList) {
        g.V().has(domainPropKeyName, P.within(graphIdList)).drop().iterate();
        g.tx().commit();
    }

    public void dropNode(Long graphId, String refId) {
        g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, refId).drop().iterate();
        g.tx().commit();
    }

    public void dropLink(Long graphId, String refId) {
        g.V().has(domainPropKeyName, graphId).bothE().has(refIdPropKeyName, refId).drop().iterate();
        g.tx().commit();
    }

    public void batchDropNodesAndLinks(Long graphId, List<String> refIdListNode, List<String> refIdListLink) {
        g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(refIdListNode)).drop().iterate();
        g.V().has(domainPropKeyName, graphId).bothE().has(refIdPropKeyName, P.within(refIdListLink)).drop().iterate();
        g.tx().commit();
    }

    public Map<String, String> createAllOneTime(Long graphId,
                           List<CategoryVO> categoryVOList, List<EdgeVO> edgeVOList,
                           Map<String, CategoryVO> cIdMap, Map<String, EdgeVO> eIdMap,
                           List<NodeVO> nodeVOList, List<LinkVO> linkVOList, boolean isBuild) {
        Map<String, String> idMap = new HashMap<>();
        long t01 = System.currentTimeMillis();
        createSchema(categoryVOList, edgeVOList, graphId, isBuild);
        long t02 = System.currentTimeMillis();
        logger.info("createSchema:  " + (t02 - t01) + " ms");
        Map<String, Vertex> vertexMap = new HashMap<>();
        try {
            long t11 = System.currentTimeMillis();
            g.V().has(domainPropKeyName, graphId).drop().iterate();
            long t12 = System.currentTimeMillis();
            logger.info("drop:  " + (t12 - t11) + " ms");
            long t21 = System.currentTimeMillis();
            for (NodeVO node: nodeVOList) {
                CategoryVO category = cIdMap.get(node.getCategoryId());
                Vertex v = createVertex(node, category.getOriginLabel().getName(), graphId);
                vertexMap.put(node.getId(), v);
                idMap.put(v.id().toString(), node.getId());
            }
            long t22 = System.currentTimeMillis();
            for (LinkVO link: linkVOList) {
                EdgeVO edge = eIdMap.get(link.getEdgeId());
                Vertex src = vertexMap.get(link.getSource());
                Vertex tar = vertexMap.get(link.getTarget());
                createEdge(src, tar, link, edge.getLabel(), graphId);
            }
            long t1 = System.currentTimeMillis();
            g.tx().commit();
            long t2 = System.currentTimeMillis();
            logger.info("createVertex:  " + (t22 - t21) + " ms");
            logger.info("createEdge:  " + (t1 - t22) + " ms");
            logger.info("commit:  " + (t2 - t1) + " ms");
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            g.tx().rollback();
        }
        return idMap;
    }

    public void rollback() {
        g.tx().rollback();
    }

    public void commit() {
        g.tx().commit();
    }

    public List<Path> shortestPathBetween2Vertex(Long graphId, String srcId, String tarId, Integer maxStep) {
        //无权最短路BFS
        List<Path> shortestPath = g.V().has(domainPropKeyName, graphId)
                .has(refIdPropKeyName, srcId).store("x")
                .repeat(__.out().where(P.without("x")).aggregate("x"))
                .until(__.has(refIdPropKeyName, tarId).or().loops().is(maxStep))
                .limit(1).path().by(refIdPropKeyName).toList();
        List<Path> validPath = new ArrayList<>();
        for (Path path: shortestPath) {
            if (path.get(path.size() - 1).equals(tarId)) {
                validPath.add(path);
            }
        }
        return validPath;
    }

    public List<Path> allPathBetween2Vertex(Long graphId, String srcId, String tarId, Integer maxStep) {
        List<Path> allPath = g.V().has(domainPropKeyName, graphId)
                .has(refIdPropKeyName, srcId)
                .repeat(__.out()).until(__.has(refIdPropKeyName, tarId).or().loops().is(maxStep))
                .simplePath().path().by(refIdPropKeyName).dedup().toList();
        List<Path> validPath = new ArrayList<>();
        for (Path p: allPath) {
            List<Object>  o = p.objects();
            String endId = (String) o.get(o.size() - 1);
            if (endId.equals(tarId)) {
                validPath.add(p);
            }
        }
        return validPath;
    }

    public List<List<Object>> shortestPathDijk(Long graphId, Object source, Object target) {
        List<List<Object>> ret = new ArrayList<>();
        Map<Object, List<EdgeWrapper>> neighborWeightMap = getNeighborWeightMap(graphId, true);
        DijkstraShortestPath dijkstra = new DijkstraShortestPath(source, neighborWeightMap, null, false);
        dijkstra.execute(target);
        List<Object> path = dijkstra.getPath(target);
        ret.add(path);
        return ret;
    }

    public Map<Object, Double> queryDistanceMapFromOne(Long graphId, Object source) {
        Map<Object, List<EdgeWrapper>> neighborWeightMap = getNeighborWeightMap(graphId, true);
        DijkstraShortestPath dijkstra = new DijkstraShortestPath(source, neighborWeightMap, null, true);
        dijkstra.execute();
        Map<Object, Double> distanceMap = dijkstra.getDistances();
        return distanceMap;
    }

    public List<String> search4attrNames(Long graphId, List<String> attrNames, Object predicate) {
        List<GraphTraversal<?, ?>> filter = new ArrayList<>();
        JanusGraphManagement mgmt = graph.openManagement();
        for (String key: attrNames) {
            String akiasKey = GraphUtil.aliasPropertyKey(key, graphId);
            Class<?> dataType =  mgmt.getPropertyKey(akiasKey).dataType();
            if (dataType == String.class) {
                filter.add(__.has(akiasKey, predicate));
            } else if (dataType == Double.class || dataType == Float.class) {
                try {
                    predicate = Double.parseDouble(predicate.toString());
                    filter.add(__.has(akiasKey, predicate));
                } catch (NumberFormatException ignored) {}

            } else if (dataType == Long.class || dataType == Integer.class) {
                try {
                    predicate = Long.parseLong(predicate.toString());
                    filter.add(__.has(akiasKey, predicate));
                } catch (NumberFormatException ignored) {}
            }
        }
        mgmt.rollback();
        List<String> mainRefIds = new ArrayList<>();
        if (filter.size() > 0) {
            List<Object> result = g.V().has(domainPropKeyName, graphId)
                    .or(filter.toArray(new GraphTraversal<?, ?>[0]))
                    .values(refIdPropKeyName).toList();
            mainRefIds = result.stream().map(Object::toString).collect(Collectors.toList());
        }

        return mainRefIds;
    }

    public List<String> adjacentWithStep(Long graphId, List<String> refIds, Integer maxStep) {
        List<Object> result = g.V().has(domainPropKeyName, graphId)
                .has(refIdPropKeyName, P.within(refIds))
                .repeat(__.both())
                .times(maxStep)
                .emit()
                .dedup()
                .values(refIdPropKeyName).toList();
        return result.stream().map(Object::toString).collect(Collectors.toList());
    }

    public List<String> queryVertexLabel(Long graphId, List<String> filter) {
        List<Object> result;
        if (filter == null || filter.size() == 0) {
            result =
                    g.V().has(domainPropKeyName, graphId)
                            .values(categoryIdPropKeyName).dedup().toList();
        } else {
            result =
                    g.V().has(domainPropKeyName, graphId)
                            .has(refIdPropKeyName, P.within(filter))
                            .values(categoryIdPropKeyName).dedup().toList();
        }
        return result.stream().map(Object::toString).collect(Collectors.toList());
    }

    public List<String> queryEdgeLabel(Long graphId, List<String> filter) {
        List<Object> result;
        if (filter == null || filter.size() == 0) {
            result =
                    g.E().has(domainPropKeyName, graphId)
                            .values(edgeIdPropKeyName).dedup().toList();
        } else {
            result =
                    g.E().has(domainPropKeyName, graphId)
                            .has(refIdPropKeyName, P.within(filter))
                            .values(edgeIdPropKeyName).dedup().toList();
        }
        return result.stream().map(Object::toString).collect(Collectors.toList());
    }

    public Map<String, Object> queryEdgeWeightRange(Long graphId, List<String> filter) {
        Map<String, Object> result = new HashMap<>();
        Object max;
        Object min;
        Optional<Comparable> optMax;
        Optional<Comparable> optMin;
        if (filter == null || filter.size() == 0) {
            optMax = g.E().has(domainPropKeyName, graphId).values(edgeWeightPropKeyName).max().tryNext();
            optMin = g.E().has(domainPropKeyName, graphId).values(edgeWeightPropKeyName).min().tryNext();
        } else {
            optMax = g.E().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .values(edgeWeightPropKeyName).max().tryNext();
            optMin = g.E().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .values(edgeWeightPropKeyName).min().tryNext();
        }
        if (!optMax.isPresent() || !optMin.isPresent()) {
            return null;
        }
        max = optMax.get();
        min = optMin.get();
        result.put("max", max);
        result.put("min", min);
        return result;
    }

    public Map<String, Object> queryAttrRange(Long graphId, List<String> filter, String key) {
        key = GraphUtil.aliasPropertyKey(key,graphId);
        Map<String, Object> result = new HashMap<>();
        Object max;
        Object min;
        Optional<Comparable> optMax;
        Optional<Comparable> optMin;
        if (filter == null || filter.size() == 0) {
            optMax = g.V().has(domainPropKeyName, graphId).values(key).max().tryNext();
            optMin = g.V().has(domainPropKeyName, graphId).values(key).min().tryNext();
        } else {
            optMax = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .values(key).max().tryNext();
            optMin = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .values(key).min().tryNext();
        }
        if (!optMax.isPresent() || !optMin.isPresent()) {
            return null;
        }
        max = optMax.get();
        min = optMin.get();
        result.put("max", max);
        result.put("min", min);
        return result;
    }

    public List<String> queryAttrDedup(Long graphId, List<String> filter, String key) {
        key = GraphUtil.aliasPropertyKey(key,graphId);
        List<Object> result;
        if (filter == null || filter.size() == 0) {
            result = g.V().has(domainPropKeyName, graphId).values(key).dedup().toList();
        } else {
            result = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .values(key).dedup().toList();
        }
        return result.stream().map(Objects::toString).collect(Collectors.toList());
    }

    public Map<Object, Long> queryAttrGroupCount(Long graphId, List<String> filter, String key) {
        key = GraphUtil.aliasPropertyKey(key,graphId);
        Map<Object, Long> result;
        if (filter == null || filter.size() == 0) {
            result =
                    g.V().has(domainPropKeyName, graphId)
                            .has(key)
                            .groupCount()
                            .by(key)
                            .order(Scope.local)
                            .by(Column.values, Order.desc)
                            .next();
        } else {
            result =
                    g.V().has(domainPropKeyName, graphId)
                            .has(refIdPropKeyName, P.within(filter))
                            .has(key)
                            .groupCount()
                            .by(key)
                            .order(Scope.local)
                            .by(Column.values, Order.desc)
                            .next();
        }
        return result;
    }

    public Map<Object, Long> queryAttrGroupCountWithLabels(Long graphId, List<String> filter, String key, List<String> categoryIds) {
        key = GraphUtil.aliasPropertyKey(key,graphId);
        Map<Object, Long> result;
        GraphTraversal<?, ?> _g;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId);
        } else {
            _g = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter));
        }
        result = _g.has(categoryIdPropKeyName, P.within(categoryIds))
                .values(key).groupCount()
                .order(Scope.local)
                .by(Column.values, Order.desc)
                .next();
        return result;
    }

    public Map<Object, Object> queryAttrGroupIdWithLabels(Long graphId, List<String> filter, String key, List<String> categoryIds) {
        key = GraphUtil.aliasPropertyKey(key,graphId);
        Map<Object, Object> result;
        GraphTraversal<?, ?> _g;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId);
        } else {
            _g = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter));
        }
        result = _g.has(categoryIdPropKeyName, P.within(categoryIds)).as("a")
                .values(key).as("b")
                .select("a").by(T.id)
                .group()
                .by(__.select("b"))
                .next();
        return result;
    }

    public JSONObject queryAttrSortWithLabels(Long graphId, List<String> filter, String key, List<String> categoryIds) {
        key = GraphUtil.aliasPropertyKey(key,graphId);
        List<Map<String, Object>> result;
        GraphTraversal<Vertex,Vertex> _g;
        Double max;
        Double min;
        Optional<Comparable> optMax;
        Optional<Comparable> optMin;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId);
            optMax = g.V().has(domainPropKeyName, graphId).has(categoryIdPropKeyName, P.within(categoryIds))
                    .values(key).max().tryNext();
            optMin = g.V().has(domainPropKeyName, graphId).has(categoryIdPropKeyName, P.within(categoryIds))
                    .values(key).min().tryNext();
        } else {
            _g = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter));
            optMax = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .has(categoryIdPropKeyName, P.within(categoryIds))
                    .values(key).max().tryNext();
            optMin = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .has(categoryIdPropKeyName, P.within(categoryIds))
                    .values(key).min().tryNext();
        }
        if (!optMax.isPresent() || !optMin.isPresent()) {
            return null;
        }
        max = Double.parseDouble(optMax.get().toString());
        min = Double.parseDouble(optMin.get().toString());
        result = _g.has(categoryIdPropKeyName, P.within(categoryIds)).as("id")
                .values(key)
                .order()
                .by(Order.desc).as("value")
                .select("id", "value")
                .by(T.id)
                .by()
                .toList();
        JSONObject ret = new JSONObject();
        ret.put("result", result);
        ret.put("max", max);
        ret.put("min", min);
        return ret;
    }

    public Map<String, Object> queryDegreeRange(Long graphId, List<String> filter, int subType) {
        Map<String, Object> result = new HashMap<>();
        Object max;
        Object min;
        Optional<Comparable> optMax;
        Optional<Comparable> optMin;
        GraphTraversal<Vertex, Edge> _g;
        if (subType == GraphFilterSubTypeEnum.TOPOLOGY_DEGREE_RANGE.getVal()) {
            _g = __.bothE();
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_IN_DEGREE_RANGE.getVal()) {
            _g = __.inE().has(edgeDirectedPropKeyName, true);
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_OUT_DEGREE_RANGE.getVal()) {
            _g = __.outE().has(edgeDirectedPropKeyName, true);
        } else {
            _g = __.bothE();
        }
        if (filter == null || filter.size() == 0) {
            optMax = g.V().has(domainPropKeyName, graphId)
                    .map(_g.asAdmin().clone().has(edgeVisiblePropKeyName, true).count())
                    .order().by(Order.desc)
                    .max().tryNext();
            optMin = g.V().has(domainPropKeyName, graphId)
                    .map(_g.asAdmin().clone().has(edgeVisiblePropKeyName, true).count())
                    .order().by(Order.desc)
                    .min().tryNext();
        } else {
            optMax = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .map(_g.asAdmin().clone().has(edgeVisiblePropKeyName, true).count())
                    .order().by(Order.desc)
                    .max().tryNext();
            optMin = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter))
                    .map(_g.asAdmin().clone().has(edgeVisiblePropKeyName, true).count())
                    .order().by(Order.desc)
                    .min().tryNext();
        }
        if (!optMax.isPresent() || !optMin.isPresent()) {
            return null;
        }
        max = optMax.get();
        min = optMin.get();
        result.put("max", max);
        result.put("min", min);
        return result;
    }



    public Map<Object, Long> queryEdgeBarChart(Long graphId, List<String> filter, String key) {
        if (!key.equals(edgeWeightPropKeyName) && !key.equals(edgeDirectedPropKeyName)) {
            key = GraphUtil.aliasPropertyKey(key,graphId);
        }
        Map<Object, Long> result;
        if (filter == null || filter.size() == 0) {
            result =
                    g.E().has(domainPropKeyName, graphId)
                            .has(key)
                            .groupCount()
                            .by(key)
                            .order(Scope.local)
                            .by(Column.keys)
                            .next();
        } else {
            result =
                    g.E().has(domainPropKeyName, graphId)
                            .has(refIdPropKeyName, P.within(filter))
                            .has(key)
                            .groupCount()
                            .by(key)
                            .order(Scope.local)
                            .by(Column.keys)
                            .next();
        }
        return result;
    }

    public GraphTraversal<?,?> filterSql(Long graphId, GraphFilterDTO filter) {
        //产生匿名遍历子句
        int subType = filter.getSubType();
        JSONObject dataObj = JSONObject.parseObject(filter.getDataJson());
        JSONArray setParams = dataObj.getJSONArray("setParams");
        if (subType == GraphFilterSubTypeEnum.NODE_CATEGORY.getVal()) {
            List<String> labels = setParams.getJSONObject(0).getJSONArray("value").toJavaList(String.class);
            return __.has(categoryIdPropKeyName, P.within(labels));
        }
        else if (subType == GraphFilterSubTypeEnum.LINK_CATEGORY.getVal()) {
            List<String> labels = setParams.getJSONObject(0).getJSONArray("value").toJavaList(String.class);
            return __.has(edgeIdPropKeyName, P.within(labels));
        }
        else if (subType == GraphFilterSubTypeEnum.LINK_WEIGHT_RANGE.getVal()) {
            List<Double> min_max = setParams.getJSONObject(0).getJSONArray("value").toJavaList(Double.class);
            return __.has(edgeWeightPropKeyName, P.gte(min_max.get(0)).and(P.lte(min_max.get(1))));
        }
        else if (subType == GraphFilterSubTypeEnum.LINK_ONLY_DIRECTED.getVal()) {
            return __.has(edgeDirectedPropKeyName, true);
        }
        else if (subType == GraphFilterSubTypeEnum.LINK_ONLY_UNDIRECTED.getVal()) {
            return __.has(edgeDirectedPropKeyName, false);
        }
        else if (subType == GraphFilterSubTypeEnum.LINK_ONLY_DOUBLE_DIRECTED.getVal()) {
            return __.has(edgeDirectedPropKeyName, false);
        }
        else if (subType == GraphFilterSubTypeEnum.LINK_REMOVE_SELF_LOOP.getVal()) {
            return __.has(edgeLoopPropKeyName, false);
        }
        else if (subType == GraphFilterSubTypeEnum.ATTR_EQUAL.getVal()) {
            String key = dataObj.getString("attr");
            Object value = setParams.getJSONObject(0).get("value");
            return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.eq(value));
        }
        else if (subType == GraphFilterSubTypeEnum.ATTR_RANGE.getVal()) {
            String key = dataObj.getString("attr");
            List<Double> min_max = setParams.getJSONObject(0).getJSONArray("value").toJavaList(Double.class);
            return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.gte(min_max.get(0)).and(P.lte(min_max.get(1))));
        }
        else if (subType == GraphFilterSubTypeEnum.ATTR_NOT_NULL.getVal()) {
            String key = dataObj.getString("attr");
            return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.neq(null));
        }
        else if (subType == GraphFilterSubTypeEnum.ATTR_CATEGORY.getVal()) {
            String key = dataObj.getString("attr");
            List<String> values = setParams.getJSONObject(0).getJSONArray("value").toJavaList(String.class);
            return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.within(values));
        }
        else if (subType == GraphFilterSubTypeEnum.ATTR_CATEGORY_COUNT.getVal()) {
            String key = dataObj.getString("attr");
            List<Long> min_max = setParams.getJSONObject(0).getJSONArray("value").toJavaList(Long.class);
            JSONObject count = dataObj.getJSONObject("count");
            List<String> values = new ArrayList<>();
            for (String attrValue: count.keySet()) {
                Integer cnt = count.getInteger(attrValue);
                if (cnt >= min_max.get(0) && cnt <= min_max.get(1)) {
                    values.add(attrValue);
                }
            }
            return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.within(values));
        }
        else if (subType == GraphFilterSubTypeEnum.ATTR_INNER_LINK.getVal()
                || subType == GraphFilterSubTypeEnum.ATTR_OUTER_LINK.getVal()) {
            String key = dataObj.getString("attr");
            JSONArray values = setParams.getJSONObject(0).getJSONArray("value");
            List<Object> attrVals = new ArrayList<>();
            for (int i = 0; i < values.size(); i++) {
                JSONObject obj = values.getJSONObject(i);
                attrVals.add(obj.get("value"));
            }
            if (subType == GraphFilterSubTypeEnum.ATTR_INNER_LINK.getVal()) {
                List<GraphTraversal<?,?>> conditionList = new ArrayList<>();
                for (Object val: attrVals) {
                    conditionList.add(
                            __.and(
                                    __.inV().has(GraphUtil.aliasPropertyKey(key, graphId), val),
                                    __.outV().has(GraphUtil.aliasPropertyKey(key, graphId), val)
                            )
                    );
                }
                if (conditionList.size() > 0) {
                    return __.or(conditionList.toArray(new GraphTraversal<?,?>[0]));
                } else {
                    return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.within(new ArrayList<>()));
                }
            }
            else if (subType == GraphFilterSubTypeEnum.ATTR_OUTER_LINK.getVal()) {
                List<GraphTraversal<?,?>> conditionList = new ArrayList<>();
                for (Object val1: attrVals) {
                    for (Object val2: attrVals) {
                        if (!val1.equals(val2)) {
                            conditionList.add(
                                    __.and(
                                            __.inV().has(GraphUtil.aliasPropertyKey(key, graphId), val1),
                                            __.outV().has(GraphUtil.aliasPropertyKey(key, graphId), val2)
                                    )
                            );
                        }
                    }
                }
                if (conditionList.size() > 0) {
                    return __.or(conditionList.toArray(new GraphTraversal<?,?>[0]));
                } else {
                    return __.has(GraphUtil.aliasPropertyKey(key, graphId), P.within(new ArrayList<>()));
                }
            }
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_DEGREE_RANGE.getVal()){
            List<Long> min_max = setParams.getJSONObject(0).getJSONArray("value").toJavaList(Long.class);
            return __.bothE().has(edgeVisiblePropKeyName, true).count().is(P.gte(min_max.get(0)).and(P.lte(min_max.get(1))));
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_IN_DEGREE_RANGE.getVal()){
            List<Long> min_max = setParams.getJSONObject(0).getJSONArray("value").toJavaList(Long.class);
            return __.inE().has(edgeDirectedPropKeyName, true).has(edgeVisiblePropKeyName, true).count().is(P.gte(min_max.get(0)).and(P.lte(min_max.get(1))));
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_OUT_DEGREE_RANGE.getVal()){
            List<Long> min_max = setParams.getJSONObject(0).getJSONArray("value").toJavaList(Long.class);
            return __.outE().has(edgeDirectedPropKeyName, true).has(edgeVisiblePropKeyName, true).count().is(P.gte(min_max.get(0)).and(P.lte(min_max.get(1))));
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_K_CORE.getVal()) {
            Long k = setParams.getJSONObject(0).getLong("value");
            int iter = 0;
            Set<Object> lastKcore = g.V().has(domainPropKeyName, graphId).id().toSet();
            Set<Object> thisKcore = new HashSet<>();
            while (iter <= 10 && !lastKcore.equals(thisKcore)) {
                lastKcore = thisKcore;
                g.V().has(domainPropKeyName, graphId)
                        .where(__.bothE().has(edgeVisiblePropKeyName, true).count().is(P.lt(k))).drop().iterate();
                thisKcore = g.V().has(domainPropKeyName, graphId).id().toSet();
                iter++;
            }
            List<Object> kCoreV = new ArrayList<>(thisKcore);
            g.tx().rollback();
            return __.hasId(kCoreV);
        }
        else if (subType == GraphFilterSubTypeEnum.TOPOLOGY_SELF_LOOP.getVal()) {
            return __.bothE().has(edgeLoopPropKeyName, true);
        }
        return null;
    }

    public FilterResult filterOnePath(Long graphId, List<Long> filterPath, Map<Long, GraphFilterDTO> filterMap) {
        List<GraphTraversal<?,?>> filterVertexStep = new ArrayList<>();
        List<GraphTraversal<?,?>> filterEdgeStep = new ArrayList<>();
        List<Object> resultV;
        List<Object> resultE;
        boolean notFlag = false;
        for (Long id: filterPath) {
            GraphFilterDTO filter = filterMap.get(id);
            int subType = filter.getSubType();
            int type = filter.getType();
            if (subType == GraphFilterSubTypeEnum.OPERATION_AND.getVal()
                    || subType == GraphFilterSubTypeEnum.OPERATION_OR.getVal()) {
                continue;
            }
            if (subType == GraphFilterSubTypeEnum.OPERATION_NOT.getVal()) {
                JSONObject filterData = JSONObject.parseObject(filter.getDataJson());
                if (filterData.get("notInput") != null) {
                    FilterResult filterResult = filterData.getObject("notInput", FilterResult.class);
                    GraphTraversal<?,?> filterSqlE = __.has(refIdPropKeyName, P.without(filterResult.getFilterE()))
                            .has(edgeVisiblePropKeyName, true);
                    GraphTraversal<?,?> filterSqlV = __.has(refIdPropKeyName, P.without(filterResult.getFilterV()));
                    if (notFlag) {
                        filterSqlE = __.not(filterSqlE);
                        filterSqlV = __.not(filterSqlV);
                        notFlag = false;
                    }
                    filterEdgeStep.add(filterSqlE);
                    filterVertexStep.add(filterSqlV);
                } else {
                    notFlag = !notFlag;
                }
                continue;
            }
            GraphTraversal<?,?> filterSql = filterSql(graphId, filter);
            if (notFlag) {
                filterSql = __.not(filterSql);
                notFlag = false;
            }
            if (type == GraphFilterTypeEnum.LINK.getVal()
                    || subType == GraphFilterSubTypeEnum.ATTR_OUTER_LINK.getVal()
                    || subType == GraphFilterSubTypeEnum.ATTR_INNER_LINK.getVal()) {
                filterEdgeStep.add(filterSql);
            } else {
                filterVertexStep.add(filterSql);
            }
        }
        if (filterVertexStep.size() > 0) {
            resultV = g.V().has(domainPropKeyName, graphId)
                    .and(filterVertexStep.toArray(new GraphTraversal<?,?>[0]))
                    .values(refIdPropKeyName)
                    .toList();
        } else {
            resultV = g.V().has(domainPropKeyName, graphId)
                    .values(refIdPropKeyName)
                    .toList();
        }

        if (filterEdgeStep.size() > 0) {
            resultE = g.E().has(domainPropKeyName, graphId)
                    .and(filterEdgeStep.toArray(new GraphTraversal<?,?>[0]))
                    .has(edgeVisiblePropKeyName, true)
                    .values(refIdPropKeyName)
                    .toList();
        } else {
            resultE = g.E().has(domainPropKeyName, graphId)
                    .has(edgeVisiblePropKeyName, true)
                    .values(refIdPropKeyName)
                    .toList();
        }


        Set<String> filterV = resultV.stream().map(Object::toString).collect(Collectors.toSet());
        Set<String> filterE = resultE.stream().map(Object::toString).collect(Collectors.toSet());
        FilterResult filterResult = new FilterResult(filterPath, filterV, filterE);
        return filterResult;
    }

    public void fixFilterResult(Long graphId, FilterResult filterResult) {
        //剔除孤立边
        Set<String> filterE = filterResult.getFilterE();
        Set<String> filterV = filterResult.getFilterV();
        List<Map<String, Object>> fixResult =
                g.E().has(domainPropKeyName, graphId)
                .has(refIdPropKeyName, P.within(filterE))
                .has(edgeVisiblePropKeyName, true)
                .project("lid", "src", "tar")
                .by(__.values(refIdPropKeyName))
                .by(__.outV().values(refIdPropKeyName))
                .by(__.inV().values(refIdPropKeyName))
                .toList();
        filterE = fixResult.stream()
                .filter(m -> filterV.contains(m.get("src").toString())
                    && filterV.contains(m.get("tar").toString()))
                .map(m -> m.get("lid").toString()).collect(Collectors.toSet());
        filterResult.setFilterE(filterE);
    }

    public boolean dropFilterMask(Long graphId, List<String> removeNids, List<String> removeLids) {
        boolean ret = false;
        if (removeNids != null && removeNids.size() > 0 ) {
            g.V().has(domainPropKeyName, graphId)
                    .has(refIdPropKeyName, P.within(removeNids)).drop().iterate();
            ret = true;
        }
        if (removeLids != null && removeLids.size() > 0) {
            g.V().has(domainPropKeyName, graphId).bothE().has(refIdPropKeyName, P.within(removeLids)).drop().iterate();
            ret = true;
        }
        return ret;
    }

    public JSONObject executeMetricsAndReport(Long graphId, List<String> removeNids, List<String> removeLids,
                                              MetricResultManager metricResultManager, String type,
                                              Long numOfNodes, Long numOfLinks, Boolean directed) {
        //value: 显示的值  detail: 报告详情  result：运行结果
        JSONObject ret = new JSONObject();
        boolean needRecover = dropFilterMask(graphId, removeNids, removeLids);
        switch (type) {
            case "topology":
                queryDensity(graphId, numOfNodes, numOfLinks, metricResultManager);
                queryMeanDegree(graphId, metricResultManager);
                queryMeanWeightedDegree(graphId, metricResultManager);
                queryDistance(graphId, metricResultManager, directed);
                querySCC(graphId, metricResultManager, directed);
                break;
            case "node":
                queryEigenvectorCentrality(graphId, metricResultManager);
                queryClusterCoeff(graphId, metricResultManager);
                break;
            case "link":
                queryDistance(graphId, metricResultManager, directed);
                break;
            default:
                queryDensity(graphId, numOfNodes, numOfLinks,metricResultManager);
                queryMeanDegree(graphId, metricResultManager);
                queryMeanWeightedDegree(graphId, metricResultManager);
                queryDistance(graphId, metricResultManager, directed);
                queryEigenvectorCentrality(graphId, metricResultManager);
        }

        if (needRecover) {
            g.tx().rollback();
        }
        return ret;
    }
//
    //metrics
    public JSONObject queryMeanDegree(Long graphId, MetricResultManager metricResultManager) {
        JSONObject ret = new JSONObject();
        Set<Object> idList = g.V().has(domainPropKeyName, graphId).id().toSet();
        Map<Object, Long> result = g.V().has(domainPropKeyName, graphId).as("a")
                .bothE().has(edgeVisiblePropKeyName, true).groupCount()
                .by(__.select("a").by(T.id))
                .next();
        idList.removeAll(result.keySet());
        idList.forEach(id->result.put(id, 0L));
        Double meanDegree = result.size() == 0 ? 0.0D : result.values().stream().mapToDouble(v->v).sum() / result.size();
        meanDegree = GraphUtil.round2DecimalDouble(meanDegree);
        metricResultManager.setDegree(
                new MetricResult(MetricResultManager.DegreeTag, result, meanDegree));
        return ret;
    }

    public JSONObject queryMeanWeightedDegree(Long graphId, MetricResultManager metricResultManager) {
        JSONObject ret = new JSONObject();
        Set<Object> idList = g.V().has(domainPropKeyName, graphId).id().toSet();
        Map<Object, Object> result = g.V().has(domainPropKeyName, graphId).as("a")
                .bothE().has(edgeVisiblePropKeyName, true).values(edgeWeightPropKeyName)
                .group().by(__.select("a")
                        .by(T.id)).by(__.sum())
                .next();
        idList.removeAll(result.keySet());
        idList.forEach(id->result.put(id, 0D));
        Double meanDegree = result.size() == 0 ? 0.0D : result.values().stream().mapToDouble(v-> Double.parseDouble(v.toString())).sum() / result.size();
        meanDegree = GraphUtil.round2DecimalDouble(meanDegree);
        metricResultManager.setWeightedDegree(
                new MetricResult(MetricResultManager.WeightedDegreeTag, result, meanDegree));
        return ret;
    }

    public JSONObject queryDensity(Long graphId, Long numOfNodes, Long numOfLinks, MetricResultManager metricResultManager) {
        JSONObject ret = new JSONObject();
//        Long N = g.V().has(domainPropKeyName, graphId).count().next();
//        Long L = g.V().has(domainPropKeyName, graphId).bothE().has(edgeVisiblePropKeyName, true).dedup().count().next();
        Double density = numOfNodes == 0 ? 0.0D : 2*numOfLinks.doubleValue() / (numOfNodes * (numOfNodes - 1));
        density = GraphUtil.round2DecimalDouble(density);
        metricResultManager.setDensity(
                new MetricResult(MetricResultManager.DensityTag, null, density));
        return ret;
    }

    public JSONObject queryEigenvectorCentrality(Long graphId, MetricResultManager metricResultManager) {
        JSONObject ret = new JSONObject();
        Map<Object, Object> result = (Map) g.V().has(domainPropKeyName, graphId)
                .repeat(__.groupCount("m").by(T.id).out())
                .times(5)
                .cap("m")
                .order(Scope.local).by(Column.values, Order.desc)
                .next();
        Map<Object, Double>  newResult = new HashMap<>();
        if (result.size() > 0) {
            Double max = ((Long) result.values().iterator().next()).doubleValue();
            Iterator<Map.Entry<Object, Object>> it = result.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<Object, Object> entry = it.next();
                Double v = ((Long)entry.getValue()) / max;
                v = GraphUtil.round2DecimalDouble(v);
                newResult.put(entry.getKey(), v);
            }
        }
        metricResultManager.setEigenvectorCentrality(
                new MetricResult(MetricResultManager.EigenvectorCentralityTag, newResult, null));
        return ret;
    }

    public JSONObject queryDistance(Long graphId, MetricResultManager metricResultManager, Boolean isDirected) {
        JSONObject ret = new JSONObject();
        Map neighborMap = getNeighborMap(graphId, isDirected);
        GraphDistance graphDistance = new GraphDistance(neighborMap, isDirected, false);
        graphDistance.execute();
        Map<Object, Double> betweennessMap = graphDistance.getBetweennessMap();
        Map<Object, Double> closenessMap = graphDistance.getClosenessMap();
        Map<Object, Double> eccentricityMap = graphDistance.getEccentricityMap();

        Map<String, Object> valueMap = new HashMap<>();
        valueMap.put("Diameter", graphDistance.getDiameter());
        valueMap.put("AvgDist", graphDistance.getAvgDist());

        metricResultManager.setBetweennessCentrality(
                new MetricResult(MetricResultManager.BetweennessCentralityTag, betweennessMap, null));
        metricResultManager.setClosenessCentrality(
                new MetricResult(MetricResultManager.ClosenessCentralityTag, closenessMap, null));
        metricResultManager.setEccentricity(
                new MetricResult(MetricResultManager.EccentricityTag, eccentricityMap, null));
        metricResultManager.setDistance(
                new MetricResult(MetricResultManager.DistanceTag, null, valueMap));
        return ret;
    }

    public JSONObject querySCC(Long graphId, MetricResultManager metricResultManager, boolean isStrongly) {
        JSONObject ret = new JSONObject();
        if (isStrongly) {
            Map neighborMap = getNeighborMap(graphId, false);
            StronglyConnectedComponents cc = new StronglyConnectedComponents(neighborMap);
            cc.execute();
            Map<Object, Integer> ccMap = cc.getSccMap();

            Map outNeighborMap = getNeighborMap(graphId, true);
            StronglyConnectedComponents scc = new StronglyConnectedComponents(outNeighborMap);
            scc.execute();
            Map<Object, Integer> sccMap = scc.getSccMap();
            metricResultManager.setWeaklyconnectedcomponents(
                    new MetricResult(MetricResultManager.WeaklyConnectedComponentsTag, ccMap, cc.getSccCount()));
            metricResultManager.setStronglyconnectedcomponents(
                    new MetricResult(MetricResultManager.StronglyConnectedComponentsTag, sccMap, scc.getSccCount()));
        } else {
            Map neighborMap = getNeighborMap(graphId, false);
            StronglyConnectedComponents cc = new StronglyConnectedComponents(neighborMap);
            cc.execute();
            Map<Object, Integer> ccMap = cc.getSccMap();
            metricResultManager.setConnectedcomponents(
                    new MetricResult(MetricResultManager.ConnectedComponentsTag, ccMap, cc.getSccCount()));
        }
        return ret;
    }

    public JSONObject queryClusterCoeff(Long graphId, MetricResultManager metricResultManager) {
        JSONObject ret = new JSONObject();
        Map neighborMap = getNeighborMap(graphId, false);
        ClusteringCoefficient cc = new ClusteringCoefficient(neighborMap);
        cc.execute();
        Map<Object, Integer> trianglesMap = cc.getTrianglesMap();
        Map<Object, Double> clusteringCoefficientMap = cc.getClusteringCoefficientMap();
        Integer totalTriangles = cc.getTotalTriangles();
        Double avgClusteringCoeff = cc.getAvgClusteringCoeff();
        metricResultManager.setClusteringCoefficient(
                new MetricResult(MetricResultManager.ClusteringCoefficientTag, clusteringCoefficientMap, avgClusteringCoeff)
        );
        metricResultManager.setTriangles(
                new MetricResult(MetricResultManager.TrianglesTag, trianglesMap, totalTriangles)
        );
        return ret;
    }


    public JSONObject pageRank(Long graphId, List<String> removeNids, List<String> removeLids, MetricResultManager metricResultManager) {
        JSONObject ret = new JSONObject();
        boolean needRecover = dropFilterMask(graphId, removeNids, removeLids);
        Set<Object> idList = g.V().has(domainPropKeyName, graphId).id().toSet();
        Map<Object, Long> outDegreeMap = g.V().has(domainPropKeyName, graphId).as("a")
                .outE().has(edgeVisiblePropKeyName, true).groupCount()
                .by(__.select("a").by(T.id))
                .next();
        idList.removeAll(outDegreeMap.keySet());
        idList.forEach(id->outDegreeMap.put(id, 0L));
        Map neighborMap = getNeighborMap(graphId, false);
        if (needRecover) {
            g.tx().rollback();
        }
        if (neighborMap.size() == 0) {
            return ret;
        }
        PageRank pageRank = new PageRank(neighborMap, outDegreeMap);
        pageRank.execute();
        Map<Object, Double> pageRankMap = pageRank.getPageRankMap();
        ret.put("detail", groupCountMap(pageRankMap));
        metricResultManager.setPageRank(
                new MetricResult(MetricResultManager.PageRankTag, pageRankMap, null));
        return ret;
    }

    public JSONObject cluster(Long graphId, List<String> removeNids, List<String> removeLids, Integer maxIter, MetricResultManager metricResultManager, Map<String, String> idMap) {
        JSONObject ret = new JSONObject();
        boolean needRecover = dropFilterMask(graphId, removeNids, removeLids);
        Map neighborMap = getNeighborMap(graphId, false);
        if (needRecover) {
            g.tx().rollback();
        }
        if (neighborMap.size() == 0) {
            return ret;
        }
        LabelPropagation lp = new LabelPropagation(neighborMap);
        if (maxIter != null) {
            lp.setMaxIter(maxIter);
        }
        try {
            lp.execute();
        } catch (Exception e) {
            return ret;
        }
        Map<Object, Integer> labelMap = lp.getLabelMap();
        Map<String, List<String>> result = new HashMap<>();
        for (Map.Entry<Object, Integer> entry: labelMap.entrySet()) {
            result.putIfAbsent(entry.getValue().toString(), new ArrayList<>());
            result.get(entry.getValue().toString()).add(idMap.get(entry.getKey().toString()));
        }
        JSONArray groupCount = new JSONArray();
        for (String clusterKey: result.keySet()) {
            JSONObject obj = new JSONObject();
            obj.put("cluster", clusterKey);
            obj.put("size", result.get(clusterKey).size());
            groupCount.add(obj);
        }
        ret.put("clusterNum", groupCount.size());
        ret.put("result", result);
        ret.put("detail", groupCount);
        JSONObject value = new JSONObject();
        value.putAll(ret);
        if (metricResultManager != null) {
            metricResultManager.setCluster(
                    new MetricResult(MetricResultManager.ClusterTag, labelMap, value));
        }
        return ret;
    }

    public static <T, P> List<Map<String, Object>> groupCountMap(Map<T, P> map) {
        Map<P, Map<String, Object>> result = new HashMap<>();
        for (P val: map.values()) {
            Map<String, Object> v = result.get(val);
            if (v == null) {
                Map<String, Object> cvMap = new HashMap<>();
                cvMap.put("value", val);
                cvMap.put("count", 0L);
                result.put(val, cvMap);
            }
            v = result.get(val);
            v.put("count", ((Long) v.get("count")) + 1);
        }
        return new ArrayList<>(result.values());
    }

    public void saveMetrics(Long graphId, MetricResult... metricResults) {
        Set<String> idList = metricResults[0].getResult().keySet();
        Set<Long> _idList = idList.stream().map(Long::parseLong).collect(Collectors.toSet());;
        List<Vertex> vertices = g.V().hasId(P.within(_idList)).toList();
        for (Vertex v: vertices) {
            GraphTraversal<?,?> _g = g.V(v);
            for (MetricResult metricResult: metricResults) {
                _g = _g.property(metricResult.getTag(), metricResult.getResult().get(v.id().toString()));
            }
            _g.iterate();
        }
        g.tx().commit();
    }

    public List<Map<Object, Object>> queryVertexDetail(Long graphId, String label, List<String> filter, Integer skip, Integer limit) {
        GraphTraversal<?, ?> _g;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId);
        } else {
            _g = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter));
        }
        List<Map<Object, Object>> ret =
                _g.has(categoryIdPropKeyName, label)
                .order().by()
                .skip(skip)
                .limit(limit)
                .valueMap()
                .toList();
        return ret;
    }

    public List<Map<String, Object>> queryEdgeDetail(Long graphId, String label, List<String> filter, Integer skip, Integer limit) {
        GraphTraversal<?, ?> _g;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId).bothE().has(edgeVisiblePropKeyName, true).dedup();
        } else {
            _g = g.V().has(domainPropKeyName, graphId).bothE().has(refIdPropKeyName, P.within(filter)).has(edgeVisiblePropKeyName, true).dedup();
        }
        List<Map<String, Object>> ret =
                _g.has(edgeIdPropKeyName, label)
                        .order().by()
                        .skip(skip)
                        .limit(limit)
                        .project("src", "tar", "kv")
                        .by(__.outV().values(orderPropKeyName))
                        .by(__.inV().values(orderPropKeyName))
                        .by(__.valueMap())
                        .toList();
        return ret;
    }


    public Long queryVertexDetailCount(Long graphId, String label, List<String> filter) {
        GraphTraversal<?, ?> _g;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId);
        } else {
            _g = g.V().has(domainPropKeyName, graphId).has(refIdPropKeyName, P.within(filter));
        }
        Long ret = _g.has(categoryIdPropKeyName, label).count().next();
        return ret;
    }

    public Long queryEdgeDetailCount(Long graphId, String label, List<String> filter) {
        GraphTraversal<?, ?> _g;
        if (filter == null || filter.size() == 0) {
            _g = g.V().has(domainPropKeyName, graphId).bothE().has(edgeVisiblePropKeyName, true).dedup();
        } else {
            _g = g.V().has(domainPropKeyName, graphId).bothE().has(refIdPropKeyName, P.within(filter)).has(edgeVisiblePropKeyName, true).dedup();
        }
        Long ret = _g.has(edgeIdPropKeyName, label).count().next();
        return ret;
    }

    public JSONObject search4LabelAndAttr(Long graphId, List<String> categoryIds, List<String> attrNames, String predicate, List<String> removeNids, List<String> removeLids) {
        List<GraphTraversal<?, ?>> filter = new ArrayList<>();
        boolean needRecover = dropFilterMask(graphId, removeNids, removeLids);
        JanusGraphManagement mgmt = graph.openManagement();
        for (String key: attrNames) {
            String akiasKey = GraphUtil.aliasPropertyKey(key, graphId);
            Class<?> dataType =  mgmt.getPropertyKey(akiasKey).dataType();
            if (dataType == String.class) {
                filter.add(__.has(akiasKey, predicate));
            }
//            else if (dataType == Double.class || dataType == Float.class) {
//                try {
//                    predicate = Double.parseDouble(predicate.toString());
//                    filter.add(__.has(akiasKey, predicate));
//                } catch (NumberFormatException ignored) {}
//
//            } else if (dataType == Long.class || dataType == Integer.class) {
//                try {
//                    predicate = Long.parseLong(predicate.toString());
//                    filter.add(__.has(akiasKey, predicate));
//                } catch (NumberFormatException ignored) {}
//            }
        }
        mgmt.rollback();
        List<String> mainRefIds = new ArrayList<>();
        if (filter.size() > 0) {
            List<Object> result = g.V().has(domainPropKeyName, graphId)
                    .has(categoryIdPropKeyName, P.within(categoryIds))
                    .or(filter.toArray(new GraphTraversal<?, ?>[0]))
                    .values(refIdPropKeyName).toList();
            mainRefIds = result.stream().map(Object::toString).collect(Collectors.toList());
        }
        if (needRecover) {
            g.tx().rollback();
        }
        JSONObject ret = new JSONObject();
        ret.put("main", mainRefIds);
        return ret;
    }

    public Map getNeighborWeightMap(Long graphId, boolean isOut) {
        //isOut=true, 默认有向边有向，无向边无向； 若需要有向边无向，则需要打开注释；
        Map<Object, List<EdgeWrapper>> neighborWeightMap = new HashMap<>();
        Map<?, ?> disMap;
        List<Object> idList = g.V().has(domainPropKeyName, graphId).id().toList();
        if (isOut) {
            disMap = g.V().has(domainPropKeyName, graphId).as("src")
//                    .outE().has(edgeVisiblePropKeyName, true).as("dis")
                    .outE().as("dis")
                    .inV().as("tar")
                    .select("dis").by(edgeWeightPropKeyName)
                    .group().by(__.select("src", "tar").by(T.id)).next();
        } else {
            disMap = g.V().has(domainPropKeyName, graphId).as("src")
                    .bothE().has(edgeVisiblePropKeyName, true).as("dis")
                    .bothV().simplePath().as("tar")
                    .select("dis").by(edgeWeightPropKeyName)
                    .group().by(__.select("src", "tar").by(T.id)).next();
        }
        for (Map.Entry<?, ?> entry: disMap.entrySet()) {
            Map oppo = (Map) entry.getKey();
            Double weight = (Double) ((ArrayList) entry.getValue()).get(0);
            Object source = oppo.get("src");
            Object target = oppo.get("tar");
            neighborWeightMap.putIfAbsent(source, new ArrayList<>());
            neighborWeightMap.get(source).add(new EdgeWrapper(source, target, weight));
        }
        for (Object id: idList) {
            neighborWeightMap.putIfAbsent(id, new ArrayList<>());
        }
        return neighborWeightMap;
    }

    public Map getNeighborMap(Long graphId, boolean isOut) {
        Map neighborMap;
        List<Object> idList = g.V().has(domainPropKeyName, graphId).id().toList();
        if (isOut) {
            neighborMap = g.V().has(domainPropKeyName, graphId).as("a")
                    .outE().has(edgeVisiblePropKeyName, true).inV().as("b")
                    .select("b").by(T.id)
                    .group().by(__.select("a").by(T.id)).next();
        } else  {
            neighborMap = g.V().has(domainPropKeyName, graphId).as("a")
                    .bothE().has(edgeVisiblePropKeyName, true).bothV().simplePath().as("b")
                    .select("b").by(T.id)
                    .group().by(__.select("a").by(T.id)).next();
        }
        for (Object id: idList) {
            neighborMap.putIfAbsent(id, new ArrayList<>());
        }
        return neighborMap;
    }

    public Map<Object, Object> queryIdMap(Long graphId) {
        List<Map<String, Object>> list = g.V().has(domainPropKeyName, graphId).as("a")
                .values(refIdPropKeyName).as("b")
                .select("a", "b")
                .toList();
        return list.stream().collect(Collectors.toMap(m->((Vertex)m.get("a")).id().toString(), m->m.get("b")));
    }

}
